package net.jeanhwea.util;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

public class ParseHtml {
	private static Map<String, Character> charMap;
	private PeekableInputStream source;
	
	private HtmlTag tag = new HtmlTag();
	private String lockedEndTag;
	
	public ParseHtml(InputStream is) {
		this.source = new PeekableInputStream(is);
		
		if (charMap == null) {
			charMap = new HashMap<String, Character>();
			charMap.put("nbsp", ' ');
			charMap.put("lt", '<');
			charMap.put("gt", '>');
			charMap.put("amp", '&');
			charMap.put("quot", '\"');
			charMap.put("bull", (char) 149);
			charMap.put("trade", (char) 129);
		}
		
	}
	
	public HtmlTag getTag() {
		return this.tag;
	}
	
	public int read() throws IOException {
		if (this.lockedEndTag != null) {
			if (peekEndTag(this.lockedEndTag)) {
				this.lockedEndTag = null;
			} else {
				return this.source.read();
			}
		}
		
		if (this.source.peek() == '<') {
			parseTag();
			if (!this.tag.isEnding() && 
					(this.tag.getName().equalsIgnoreCase("script") || 
							this.tag.getName().equalsIgnoreCase("style"))) {
				this.lockedEndTag = this.tag.getName().toLowerCase();
			}
			return 0;
		} else if (this.source.peek() == '&') {
			return parseSpecialCharacter();
		} else {
			return this.source.read();
		}
	}
	
	@Override
	public String toString(){
		try{
			StringBuilder ret = new StringBuilder();
			int ch = 0;
			StringBuilder text = new StringBuilder();
			do {
				ch = read();
				if (ch == 0) {
					if(text.length() > 0){
						System.out.println("Text: "+ text.toString());
						text.setLength(0);
					}
					System.out.println("Tag: " + getTag());
				} else if(ch != -1){
					ret.append((char) ch);
				}
			}while(ch != -1);
			if(text.length() > 0){
				System.out.println("Text: " +text.toString().trim());
			}
			return ret.toString();
		} catch(IOException e){
			return "[IO Error]";
		}
	}
	
	protected String parseString() throws IOException {
		StringBuilder ret = new StringBuilder();
		eatWhitespace();
		
		if ("\"\'".indexOf(this.source.peek()) != -1) {
			int delim = this.source.peek();
			while((this.source.peek() != delim) &&
					(this.source.peek() != -1)) {
				if(ret.length() > 1000) {
					break;
				}
				int ch = parseSpecialCharacter();
				if((ch == 10) || (ch == 13)) {
					continue;
				}
				ret.append((char) ch);
			}
			if("\"\'".indexOf(this.source.peek()) != -1) {
				this.source.read();
			}
		} else {
			while(!Character.isWhitespace(this.source.peek()) &&
					(this.source.peek() != -1) &&
						(this.source.peek() != '>')) {
				ret.append(parseSpecialCharacter());
			}
		}
		
		return ret.toString();
	}

	protected void eatWhitespace() throws IOException {
		while(Character.isWhitespace(this.source.peek())){
			this.source.read();
		}
	}

	protected void parseTag() throws IOException {
		this.tag.clear();
		StringBuilder tagName = new StringBuilder();
		
		this.source.read();
		
		// comments go first
		if ((this.source.peek(0) == '!') && 
				(this.source.peek(1) == '-') && 
					(this.source.peek(2) == '-')) {
			while (this.source.peek() != -1) {
				if( (this.source.peek(0) == '-') && 
						(this.source.peek(1) == '-') && 
							(this.source.peek(2) == '>')){
					break;
				}
				if(this.source.peek() != '\r') {
					tagName.append((char) this.source.peek());
				}
				this.source.read();
			}
			tagName.append("--");
			this.source.read();
			this.source.read();
			this.source.read();
		}
		
		// tag name
		while(this.source.peek() != -1) {
			if(Character.isWhitespace((char) this.source.peek())||
					(this.source.peek() == '>') ){
				break;
			}
			tagName.append(this.source.read());
		}
		
		// tag attribution
		while ((this.source.peek() != '>') && 
					(this.source.peek() != -1)){
			String attriName = parseAttributeName();
			String attriValue = null;
			if (attriName.equals("/")) {
				eatWhitespace();
				if (this.source.peek() == '>') {
					this.tag.setEnding(true);
					break;
				}
			}
		
			// tag value
			eatWhitespace();
			if (this.source.peek() == '=') {
				this.source.read();
				attriValue = parseString();
			}
			this.tag.setAttribute(attriName, attriValue);
		}
		
		this.source.read();
	}


	protected String parseAttributeName() throws IOException {
		eatWhitespace();
		
		if("\"\'".indexOf(this.source.peek()) == -1){
			StringBuilder ret = new StringBuilder();
			while((!Character.isWhitespace(this.source.peek())) &&
					(this.source.peek() != '=') &&
						(this.source.peek() != '>') &&
							(this.source.peek() != -1)) {
				int ch = parseSpecialCharacter();
				ret.append((char) ch);
			}
			return ret.toString();
		} else {
			return (parseString());
		}
	}

	private char parseSpecialCharacter() throws IOException {
		char ret = (char) this.source.read();
		int advanceBy = 0;
		
		if(ret == '&') {
			int ch = 0;
			StringBuilder buffer = new StringBuilder();
			
			do {
				ch = this.source.peek(advanceBy++);
				if((ch != '&') && (ch != ';') && !Character.isWhitespace(ch)){
					buffer.append((char) ch);
				}
			} while ((ch != ';') && (ch != -1) && !Character.isWhitespace(ch));
		
			String b = buffer.toString().trim().toLowerCase();
			if(b.length() > 0) {
				if(b.charAt(0) == '#') {
					try{
						ret = (char) Integer.parseInt(b.substring(1));
					} catch(NumberFormatException e){
						advanceBy = 0;
					}
				}else {
					if(charMap.containsKey(b)){
						ret = charMap.get(b);
					} else {
						advanceBy = 0;
					}
				}
			} else {
				advanceBy = 0;
			}
			
		}
		
		return ret;
	}
	
	private boolean peekEndTag(String name) throws IOException {
		/**
		 * notice that end tag is like this :
		 *  < / tag >
		 */
		int i = 0;
		
		//skip whitespace
		while( (this.source.peek(i) != -1) && 
				(Character.isWhitespace(this.source.peek(i)))
			) { i++; }
		
		if(this.source.peek(i) != '<'){
			return false;
		} else {
			i ++;
		}
		
		// peek over all whitespaces
		while( (this.source.peek(i) != -1) && 
				(Character.isWhitespace(this.source.peek(i)))
			) { i++; }
		
		if(this.source.peek(i) != '/'){
			return false;
		} else {
			i ++;
		}
		
		while( (this.source.peek(i) != -1) && 
				(Character.isWhitespace(this.source.peek(i)))
			) { i++; }
		
		for (int j = 0; j < name.length(); j++) {
			if(Character.toLowerCase(this.source.peek(i)) 
					!= Character.toLowerCase(name.charAt(j)) ) {
				return false;
			}
			i ++;
		}
		return true;
	}
	
}